package com.five.zhh.fiveinfive.room;

import cn.hutool.core.util.BooleanUtil;
import cn.hutool.json.JSONUtil;
import com.five.zhh.fiveinfive.room.connection.UserRoomConnection;
import com.five.zhh.fiveinfive.room.entity.pojo.Chess;
import com.five.zhh.fiveinfive.room.message.OperateTittle;
import com.five.zhh.fiveinfive.room.message.UserOperate;
import com.five.zhh.fiveinfive.room.message.UserOperateResult;
import com.five.zhh.fiveinfive.room.message.operate.*;
import com.five.zhh.fiveinfive.room.message.result.PlayChessResult;
import com.five.zhh.fiveinfive.room.user.UserInfo;
import com.five.zhh.fiveinfive.room.user.UserInfoInRoom;
import com.five.zhh.fiveinfive.room.util.ChessUtil;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 一个房间有两个操作席和多个观战席
 */

@Slf4j
@Data
public class GameRoom {

    private String roomCode;

    private long createTimestamp = System.currentTimeMillis();

    private AtomicInteger blackScore = new AtomicInteger(0);

    private AtomicInteger whiteScore = new AtomicInteger(0);

    private volatile UserRoomConnection blackUser;

    /**
     * 黑子准备
     */
    private AtomicBoolean blackReady = new AtomicBoolean(false);

    private volatile UserRoomConnection whiteUser;

    /**
     * 白子准备
     */
    private AtomicBoolean whiteReady = new AtomicBoolean(false);

    /**
     * 先手标记
     * T：黑子
     * F：白子
     */
    private AtomicBoolean firstStep = new AtomicBoolean(true);

    /**
     * 当前回合骑手
     * T：黑子
     * F：白子
     */
    private AtomicBoolean currentTurn = new AtomicBoolean(true);

    /**
     * 房间是否在游戏中
     */
    private AtomicBoolean running = new AtomicBoolean(false);

    /**
     * 棋盘点位
     * 0：当前无子
     * 1：当前黑子
     * 2：当前白子
     */
    private final Chess[][] chessboard;

    private final int boardSize;


    /**
     * userId : userRoomConnection
     */
    private Map<String, UserRoomConnection> players = new ConcurrentHashMap<>();

    /**
     * userId : userRoomConnection
     */
    private Map<String, UserRoomConnection> spectators = new ConcurrentHashMap<>();

    public GameRoom(int size, String code,  Chess[][] chessboard) {
        this.boardSize = size;
        this.roomCode = code;
        this.chessboard = chessboard;
    }


    public boolean addPlayer(UserRoomConnection userRoomConnection) {
        UserInfo currentUser = userRoomConnection.getUserInfo();
        // 如果已存在
        if (players.containsKey(currentUser.getUserId())) {
            players.put(currentUser.getUserId(), userRoomConnection);
            if (this.blackUser != null && this.blackUser.getUserInfo().getUserId().equals(currentUser.getUserId())) {
                this.blackUser = userRoomConnection;
            } else if (this.whiteUser != null && this.whiteUser.getUserInfo().getUserId().equals(currentUser.getUserId())) {
                this.whiteUser = userRoomConnection;
            }
            return true;
        }
        if (players.size() >= 2 && this.blackUser != null && this.whiteUser != null) {
            return false;
        }
        // 先进来的黑子，后进的白子
        if (this.blackUser == null) {
            this.blackUser = userRoomConnection;
        } else if (this.whiteUser == null) {
            this.whiteUser = userRoomConnection;
        }
        if (this.blackUser != null && this.blackUser.getUserInfo().getUserId().equals(userRoomConnection.getUserInfo().getUserId())) {
            this.blackUser = userRoomConnection;
        } else if (this.whiteUser != null && this.whiteUser.getUserInfo().getUserId().equals(userRoomConnection.getUserInfo().getUserId())) {
            this.whiteUser = userRoomConnection;
        }
        players.put(userRoomConnection.getUserInfo().getUserId(), userRoomConnection);
        return true;
    }

    public boolean addSpectator(UserRoomConnection userRoomConnection) {
        spectators.put(userRoomConnection.getUserInfo().getUserId(), userRoomConnection);
        return true;
    }

    public void removeUser(UserRoomConnection connection) {
        this.removePlayer(connection);
        this.removeSpectator(connection);
    }

    public void removePlayer(UserRoomConnection userRoomConnection) {
        UserInfo userInfo = userRoomConnection.getUserInfo();
        int role = this.getUserRoleInRoom(userInfo);
        if (role == 1) {
            this.blackUser = null;
        } else if (role == 2) {
            this.whiteUser = null;
        }
        players.remove(userInfo.getUserId());
        operate(new SystemBroadcastOperate(roomCode, "[" + userRoomConnection.getUserInfo().getUserName() + "]离开房间"));
    }

    public void removeSpectator(UserRoomConnection userRoomConnection) {
        spectators.remove(userRoomConnection.getUserInfo().getUserId());
    }

    public void close() {
        GameRoomManager.removeGameRoom(this.roomCode);
    }

    public UserOperateResult operate(UserOperate operate) {
        if (operate instanceof SystemBroadcastOperate system) {
            return this.broadcast(system);
        } else if (operate instanceof UserMessageOperate message) {
            return this.chat(message);
        } else if (operate instanceof PlayChessOperate playChess) {
            UserInfo userInfo = playChess.getSender();
            playChess.setColor(this.getUserRoleInRoom(userInfo));
            return this.playChess(playChess);
        } else if (operate instanceof GiveUpOperate giveUp) {
            return this.giveUp(giveUp);
        } else if (operate instanceof SystemOperate system) {
            return this.systemMessage(system);
        } else if (operate instanceof WinnerOperate winner) {
            return this.winner(winner);
        } else if (operate instanceof LoserOperate loser) {
            return this.lose(loser);
        }
        return new UserOperateResult(OperateTittle.SYSTEM);
    }

    /**
     * 系统广播
     * 把全部人都发一遍
     *
     * @return
     */
    public UserOperateResult broadcast(SystemBroadcastOperate o) {
        if (null == o) {
            return null;
        }
        this.commitMessage(o);
        return UserOperateResult.defaultResult(o.getTittle(), o.getRoomCode());
    }

    /**
     * 玩家发送消息到聊天群内，除了自己都发
     *
     * @return
     */
    public UserOperateResult chat(UserMessageOperate o) {
        if (o == null) return null;
        commitMessageExcludeSender(o.getSender(), o);
        return UserOperateResult.defaultResult(o.getTittle(), o.getRoomCode());
    }

    /**
     * 下棋操作
     *
     * @return
     */
    public UserOperateResult playChess(PlayChessOperate o) {
        if (o == null || o.getColor() == 0) return null;
        if (currentTurn.get()) {
            if (o.getColor() != 1) {
                return PlayChessResult.fail(
                        o.getColor(), o.getX(), o.getY(),
                        "当前回合为黑子",
                        new UserInfoInRoom(o.getSender(), this.getUserRoleInRoom(o.getSender())),
                        o.getRoomCode());
            }
        } else {
            if (o.getColor() != 2) {
                return PlayChessResult.fail(
                        o.getColor(), o.getX(), o.getY(),
                        "当前回合为白子",
                        new UserInfoInRoom(o.getSender(), this.getUserRoleInRoom(o.getSender())),
                        o.getRoomCode());
            }
        }
        int x = o.getX();
        int y = o.getY();
        if (!ChessUtil.isChessInBoard(chessboard, x, y)) {
            return PlayChessResult.fail(
                    o.getColor(), o.getX(), o.getY(),
                    "不能下在棋盘外边",
                    new UserInfoInRoom(o.getSender(), this.getUserRoleInRoom(o.getSender())),
                    o.getRoomCode());
        }
        if (ChessUtil.hasChess(chessboard, x, y)) {
            return PlayChessResult.fail(
                    o.getColor(), o.getX(), o.getY(),
                    "该位置已有棋子",
                    new UserInfoInRoom(o.getSender(), this.getUserRoleInRoom(o.getSender())),
                    o.getRoomCode());
        }
        chessboard[x][y] = new Chess(x, y, o.getColor());
        commitMessageExcludeSender(o.getSender(), o);
        this.changeCurrentTurn();
        return PlayChessResult.success(o.getColor(), x, y, new UserInfoInRoom(o.getSender(), this.getUserRoleInRoom(o.getSender())), o.getRoomCode());
    }

    public UserOperateResult giveUp(GiveUpOperate o) {
        if (o == null) return null;
        if (BooleanUtil.isTrue(o.getIsGiveUp())) {
            this.synGiveUp(o);
        } else if (BooleanUtil.isTrue(o.getIsAccept())) {
            this.acceptGiveUp(o);
        }
        return UserOperateResult.defaultResult(o.getTittle(), o.getRoomCode());
    }

    /**
     * 发起投降
     */
    private void synGiveUp(GiveUpOperate o) {
        UserInfo userInfo = o.getUserInfo();
        String targetUserId = userInfo.getUserId();
        // 目标ID是对手
        int role = this.getUserRoleInRoom(userInfo);
        if (role == 1) {
            targetUserId = this.whiteUser.getUserInfo().getUserId();
        } else if (role == 2) {
            targetUserId = this.blackUser.getUserInfo().getUserId();
        }
        // 把投降报文发送给对手
        this.commitMessageForOne(o, targetUserId);
    }

    /**
     * 接受投降
     * 发送消息并清空当前对局，发起投降一方判输
     *
     * @return 接受：True，不接受：False
     */
    private void acceptGiveUp(GiveUpOperate o) {
        // 接受方为胜利方
        Boolean accept = o.getAccept();
        UserInfo owner = o.getSender();
        UserInfo target = null;
        if (this.blackUser.getUserInfo().getUserId().equals(owner.getUserId())) {
            target = this.whiteUser.getUserInfo();
        } else {
            target = this.blackUser.getUserInfo();
        }
        if (accept) {
            if (this.blackUser.getUserInfo().getUserId().equals(owner.getUserId())) {
                this.blackScore.incrementAndGet();
            } else {
                this.whiteScore.incrementAndGet();
            }
            this.restart();
            // 给己方发送胜利报文，给对方发送失败报文
            this.commitMessageForOne(new WinnerOperate(owner), owner.getUserId());
            this.commitMessageForOne(new LoserOperate(target), target.getUserId());
            // 广播胜利方
            this.operate(new SystemBroadcastOperate(roomCode, "[游戏结束]" + owner.getUserName() + "获胜"));
        } else {
            // 不接受则直接给对手转发这个不接受报文
            this.commitMessageForOne(o, target.getUserId());
        }
    }

    /**
     * 发送系统反馈
     *
     * @return
     */
    private UserOperateResult systemMessage(SystemOperate o) {
        if (SystemOperate.JOIN_ROOM_SUCCESS == o.getCode()) {
            UserInfo currentUser = o.getCurrentUser();
            commitMessageForOne(o, currentUser.getUserId());
        } else if (SystemOperate.PLAYER_READY == o.getCode()) {
            int role = this.getUserRoleInRoom(o.getSender());
            if (role == 1) {
                this.blackReady();
            } else if (role == 2) {
                this.whiteReady();
            }
            boolean startSuccess = this.gameStart();
            if (startSuccess) {
                // 发送先后手消息

                UserInfoInRoom bu = new UserInfoInRoom(this.blackUser.getUserInfo(), role);
                UserInfoInRoom wu = new UserInfoInRoom(this.whiteUser.getUserInfo(), role);
                if (firstStep.get()) {
                    bu.setFirst(true);
                    wu.setFirst(false);
                } else {
                    bu.setFirst(false);
                    wu.setFirst(true);
                }
                SystemOperate playOrderMessage = new SystemOperate(roomCode, SystemOperate.PLAY_ORDER, "玩家先后手顺序");
                playOrderMessage.setCurrentUser(bu);
                this.operate(playOrderMessage);
                playOrderMessage.setCurrentUser(wu);
                this.operate(playOrderMessage);
                Map<String, Boolean> playerOrders = new HashMap<>(2);
                playerOrders.put("b", bu.isFirst());
                playerOrders.put("w", wu.isFirst());
                this.operate(new SystemOperate(roomCode, SystemOperate.FIRST_PLAY, JSONUtil.toJsonStr(playerOrders)));
                // 游戏开始
                this.operate(
                        new SystemOperate(roomCode, SystemOperate.GAME_START, "游戏开始")
                );
            }
        } else if (SystemOperate.GAME_START == o.getCode()) {
            this.commitMessage(o);
        } else if (SystemOperate.PLAY_CHESS_FAIL == o.getCode()) {
            this.commitMessageForOne(o, o.getCurrentUser().getUserId());
        } else if (SystemOperate.PLAY_ORDER == o.getCode()) {
            this.commitMessageForOne(o, o.getCurrentUser().getUserId());
        } else if (SystemOperate.FIRST_PLAY == o.getCode()) {
            this.commitMessage(o);
        } else if (SystemOperate.OTHER_JOIN == o.getCode()) {
            this.commitMessageExcludeSender(o.getSender(), o);
        }
        return UserOperateResult.defaultResult(o.getTittle(), o.getRoomCode());
    }

    /**
     * 玩家胜利
     */
    private UserOperateResult winner(WinnerOperate o) {
        if (o == null) return null;
        // 给胜利方发送胜利报文，给对方发送失败报文
        this.commitMessageForOne(o, o.getWinner().getUserId());
        UserInfo loser = blackUser.getUserInfo();
        if (loser.getUserId().equals(o.getWinner().getUserId())) {
            loser = whiteUser.getUserInfo();
        }
        LoserOperate loserOperate = new LoserOperate(loser);
        this.operate(loserOperate);
        if (this.blackUser.getUserInfo().getUserId().equals(o.getWinner().getUserId())) {
            this.blackScore.incrementAndGet();
        } else if (this.whiteUser.getUserInfo().getUserId().equals(o.getWinner().getUserId())) {
            this.whiteScore.incrementAndGet();
        }
        // 重置游戏
        this.restart();
        return UserOperateResult.defaultResult(o.getTittle(), o.getRoomCode());
    }

    private UserOperateResult lose(LoserOperate o) {
        if (null == o) return null;
        this.commitMessageForOne(o, o.getLoser().getUserId());
        return UserOperateResult.defaultResult(o.getTittle(), o.getRoomCode());
    }

    private void commitMessageExcludeSender(UserInfo sender, UserOperate operate) {
        String sendMessageContent = JSONUtil.toJsonStr(operate);
        try {
            for (Map.Entry<String, UserRoomConnection> entry : players.entrySet()) {
                String k = entry.getKey();
                UserRoomConnection v = entry.getValue();
                if (sender.getUserId().equals(k)) {
                    continue;
                }
                v.getSession().getBasicRemote().sendText(sendMessageContent);
            }
            for (Map.Entry<String, UserRoomConnection> entry : spectators.entrySet()) {
                String k = entry.getKey();
                UserRoomConnection v = entry.getValue();
                if (sender.getUserId().equals(k)) {
                    continue;
                }
                v.getSession().getBasicRemote().sendText(sendMessageContent);
            }
        } catch (Exception e) {
            log.error("commitMessageExcludeSender: {}", e.getMessage());
        }
    }

    private void commitMessage(UserOperate o) {
        String sendMessageContent = JSONUtil.toJsonStr(o);
        try {
            for (Map.Entry<String, UserRoomConnection> entry : players.entrySet()) {
                UserRoomConnection v = entry.getValue();
                v.getSession().getBasicRemote().sendText(sendMessageContent);
            }
            for (Map.Entry<String, UserRoomConnection> entry : spectators.entrySet()) {
                UserRoomConnection v = entry.getValue();
                v.getSession().getBasicRemote().sendText(sendMessageContent);
            }
        } catch (
                Exception e) {
            log.error("commitMessage: {}", e.getMessage());
        }
    }

    private void commitMessageForOne(UserOperate o, String targetUserId) {
        String sendMessageContent = JSONUtil.toJsonStr(o);
        UserRoomConnection userRoomConnection = players.get(targetUserId);
        if (userRoomConnection == null) {
            userRoomConnection = spectators.get(targetUserId);
        }
        if (userRoomConnection == null) {
            return;
        }
        try {
            userRoomConnection.getSession().getBasicRemote().sendText(sendMessageContent);
        } catch (Exception e) {
            log.error("commitMessageForOne: {}", e.getMessage());
        }
    }

    /**
     * 获取玩家在房间里的角色
     *
     * @return 1：黑子，2：白子，3：旁观者，0：玩家不在本房间
     */
    public int getUserRoleInRoom(UserInfo userInfo) {
        if (this.blackUser != null && this.blackUser.getUserInfo().getUserId().equals(userInfo.getUserId())) {
            return 1;
        } else if (this.whiteUser != null && this.whiteUser.getUserInfo().getUserId().equals(userInfo.getUserId())) {
            return 2;
        }
        return 0;
    }

    private void restart() {
        this.clearChessboard();
        this.changeFirst();
        this.changeCurrentTurn(this.firstStep.get());
        this.resetReadyStatus();
        this.running.set(!this.running.get());
    }

    private void changeCurrentTurn() {
        this.currentTurn.set(!this.currentTurn.get());
    }

    private void changeCurrentTurn(boolean setter) {
        this.currentTurn.set(setter);
    }

    private void changeFirst() {
        this.firstStep.set(!this.firstStep.get());
    }

    private void resetReadyStatus() {
        this.blackReady.set(false);
        this.whiteReady.set(false);
    }

    private void clearChessboard() {
        for (Chess[] chess : this.chessboard) {
            Arrays.fill(chess, null);
        }
    }

    public synchronized boolean gameStart() {
        if (this.blackReady.get() && this.whiteReady.get()) {
            this.running.set(true);
            return true;
        }
        return false;
    }

    public boolean blackReady() {
        return this.blackReady.getAndSet(true);
    }

    public boolean whiteReady() {
        return this.whiteReady.getAndSet(true);
    }

    public boolean isWin(int x, int y) {
        return ChessUtil.isWin(chessboard, chessboard[x][y]);
    }

}
